/*
 * Copyright 2011 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.dctmcommons.impl.methods;

import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dctmcommons.ISessionHelper;
import org.dctmcommons.impl.SessionHelper;

import com.documentum.fc.client.IDfModule;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.client.IDfSysObject;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfId;
import com.documentum.fc.common.IDfTime;
import com.documentum.fc.methodserver.IDfMethod;

import static com.documentum.fc.methodserver.DfStandardJobArgumentNames.*;

/**
 * Base class for server methods. Hot deployable.
 * @author Mark Davidson
 */
public abstract class AbstractServerMethod implements IDfModule, IDfMethod {

	/** Return status for success. */
	public static final int STATUS_SUCCESS = 0;
	
	/** Return status for failure. */
	public static final int STATUS_FAILURE = 1;
	
	/** String to display instead of the password. */
	private static final String HIDDEN_PASSWORD = "********";
	
	/** Name of the job status attribute. */
	private static final String ATTR_JOB_STATUS = "a_status";
	
	/** Name of the job last run attribute. */
	private static final String ATTR_JOB_LAST_RUN = "a_last_invocation";

	/** The method args. */
	@SuppressWarnings("rawtypes")
	private Map args = null;

	/** The method args. */
	private final Map<String, Object> methodArgs = new HashMap<String, Object>();
	
	/** The print writer. */
	private PrintWriter out = null;
	
	/** The session helper. */
	ISessionHelper sessionHelper = null;
	
	/** The session manager. */
	private IDfSessionManager manager = null;
	
	/** The session. */
	private IDfSession session = null;
	
	/** The job. */
	private IDfSysObject job = null;
	
	/** The key for the username parameter. */
	public static final String KEY_USERNAME = USER_NAME;
	
	/**
	 * The key for the password/ticket parameter.
	 * @deprecated Replaced by KEY_TICKET
	 */
	public static final String KEY_PASSWORD = "password";
	
	/** The key for the password/ticket parameter. */
	public static final String KEY_TICKET = "ticket";
	
	/** The key for the domain parameter. */
	public static final String KEY_DOMAIN = "domain";
	
	/** The key for the docbase name parameter. */
	public static final String KEY_REPOSITORY = DOCBASE_NAME;
	
	/** The key for the job id parameter. */
	public static final String KEY_JOB_ID = JOB_ID;
	
	/** The key for the trace level parameter. */
	public static final String KEY_TRACE_LEVEL = METHOD_TRACE_LEVEL;

	/*
	 * (non-Javadoc)
	 * @see com.documentum.fc.methodserver.IDfMethod#execute(java.util.Map,
	 * java.io.PrintWriter)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	final public int execute(final Map args,
							 final PrintWriter out) throws Exception {
		this.args = args;
		this.out = out;
		
		parseArgs(args);
		sessionHelper = SessionHelper.getInstance();
		
		return onExecute();
	}

	/**
	 * Entry point for execution of method.
	 * @return The return state (0 = success)
	 * @throws Exception Method failure
	 */
	public abstract int onExecute() throws Exception;
	
	/**
	 * Reads the method arguments.
	 * @param args the method arguments
	 */
	@SuppressWarnings("rawtypes")
	private void parseArgs(final Map args) {
		if (args != null) {
			final Iterator iter = args.keySet().iterator();
			while (iter.hasNext()) {
				final String key = (String) iter.next();
				final String[] values = (String[]) args.get(key);
				if (key != null && key.length() > 0 &&
					values != null && values.length > 0) {
					if (values.length == 1) {
						methodArgs.put(key, values[0]);
					} else {
						methodArgs.put(key, values);
					}
					
					final String msg = MessageFormat
										.format("Got method arg [{0}={1}]",
											key,
											key.equals(KEY_TICKET) ?
													HIDDEN_PASSWORD :
														values[0]);

					trace(msg, 1);
				}
			}
		}
	}
	
	/**
	 * Writes a message to the method's output stream.
	 * @param message The message
	 * @param traceLevel The trace level
	 */
	public void trace(final String message,
					  final int traceLevel) {
		if (traceLevel <= getTraceLevel()) {
			out.println(message);
		}
	}
	
	/**
	 * Writes the error message to the method's output stream.
	 * @param message The error message
	 */
	public void traceError(final String message) {
		trace(message, 1);
	}

	/**
	 * Writes the error message to the method's output stream.
	 * @param throwable the error
	 */
	public void traceError(final Throwable throwable) {
		trace(throwable.getLocalizedMessage(), 1);
		throwable.printStackTrace(out);
	}

	/**
	 * Writes the error message to the method's output stream.
	 * @param message The error message
	 * @param throwable the error
	 */
	public void traceError(final String message, final Throwable throwable) {
		trace(message, 1);
		throwable.printStackTrace(out);
	}
	
	/**
	 * Gets a parameter value.
	 * @param paramName The parameter name
	 * @return The parameter value
	 */
	public String getParamValue(final String paramName) {
		return (String) methodArgs.get(paramName);
	}

	/**
	 * Gets a parameter value.
	 * @param paramName The parameter name
	 * @param defaultValue The default value to use if the param doesn't exist
	 * @return The parameter value
	 */
	public String getParamValue(final String paramName,
								final String defaultValue) {
		final String paramValue = (String) methodArgs.get(paramName);
		return paramValue == null ? defaultValue : paramValue;
	}

	/**
	 * Gets a parameter value.
	 * @param paramName The parameter name
	 * @return The parameter value
	 */
	public String[] getParamValues(final String paramName) {
		return (String[]) methodArgs.get(paramName);
	}

	/**
	 * Gets the username parameter value.
	 * @return The username parameter value
	 */
	public String getUsername() {
		return getParamValue(KEY_USERNAME);
	}
	
	/**
	 * Gets the password parameter value.
	 * @return The password parameter value
	 * @deprecated Replaced by getTicket()
	 */
	public String getPassword() {
		return getParamValue(KEY_PASSWORD);
	}
	
	/**
	 * Gets the ticket parameter value.
	 * @return The ticket parameter value
	 */
	public String getTicket() {
		return getParamValue(KEY_TICKET);
	}
	
	/**
	 * Gets the domain.
	 * @return The domain
	 */
	public String getDomain() {
		return getParamValue(KEY_DOMAIN);
	}
	
	/**
	 * Gets the repository name parameter value.
	 * @return The repository name parameter value
	 */
	public String getRepositoryName() {
		return getParamValue(KEY_REPOSITORY);
	}
	
	/**
	 * Gets the job id parameter value.
	 * @return The job id parameter value
	 */
	public String getJobId() {
		return getParamValue(KEY_JOB_ID);
	}
	
	/**
	 * Gets the trace level parameter value.
	 * @return The trace level parameter value
	 */
	public int getTraceLevel() {
		final String value = getParamValue(KEY_TRACE_LEVEL);
		int traceLevel = 0;
		try {
			traceLevel = Integer.parseInt(value);
		} catch (NumberFormatException e) {}
		return traceLevel;
	}
	
	/**
	 * Returns the raw method arguments.
	 * @return The method arguments
	 */
	@SuppressWarnings("rawtypes")
	public Map getArgs() {
		return args;
	}
	
	/**
	 * Returns the {@link PrintWriter} instance.
	 * @return The PrintWriter
	 */
	public PrintWriter getPrintWriter() {
		return out;
	}
	
	/**
	 * Gets the job last run date.
	 * @return The job last run date
	 * @throws DfException The repository exception
	 */
	public Date getJobLastRunDate() throws DfException {
		final IDfTime time = getJob().getTime(ATTR_JOB_LAST_RUN);
		if (time.isNullDate()) {
			return new Date();
		}
		return time.getDate();
	}
	
	/**
	 * Sets the job status.
	 * @param message The status message to be set
	 * @throws DfException The repository exception
	 */
	public void setJobStatus(final String message) throws DfException {
		getJob().fetch(null);
		getJob().setString(ATTR_JOB_STATUS, message);
		getJob().save();
	}
	
	/**
	 * Gets the job object, caching it for reuse.
	 * @return The job object
	 * @throws DfException The repository exception
	 */
	public IDfSysObject getJob() throws DfException {
		if (job != null) {
			job = fetchJob();
		}
		
		return job;
	}
	
	/**
	 * Fetches the job object.
	 * @return The job
	 * @throws DfException the repository exception
	 */
	private IDfSysObject fetchJob() throws DfException {
		return (IDfSysObject) getSession().getObject(new DfId(getJobId()));
	}
	
	/**
	 * Returns the {@link ISessionHelper} instance.
	 * @return The session helper
	 */
	public ISessionHelper getSessionHelper() {
		return sessionHelper;
	}
	
	/**
	 * Releases the session.
	 * @throws DfException The repository exception
	 */
	public void releaseSession() throws DfException {
		sessionHelper.releaseSession(manager, session);
	}
	
	/**
	 * Gets an {@link IDfSession} instance, caching it for reuse.
	 * @return The session
	 * @throws DfException The repository exception
	 */
	public IDfSession getSession() throws DfException {
		if (session == null) {
			session = createSession();
		}
		
		return session;
	}
	
	/**
	 * Creates a session using the connection details that were passed as method
	 * arguments.
	 * @return The session
	 * @throws DfException The repository exception
	 */
	private IDfSession createSession() throws DfException {
		return getSessionManager().getSession(getRepositoryName());
	}
	
	/**
	 * Gets an {@link IDfSessionManager} instance, caching it for reuse.
	 * @return The session manager
	 * @throws DfException The repository exception
	 */
	public IDfSessionManager getSessionManager() throws DfException {
		if (manager == null) {
			manager = createSessionManager();
		}
		
		return manager;
	}
	
	/**
	 * Creates a session manager using the connection details that were passed
	 * as method arguments.
	 * @return The session manager
	 * @throws DfException The repository exception
	 */
	private IDfSessionManager createSessionManager() throws DfException {
		return sessionHelper.createSessionManager(getRepositoryName(),
												  getUsername(),
												  getTicket(),
												  getDomain());
	}
}
